/**
 * Error handling utilities
 */

/**
 * Base error class for ComfyUI SDK
 */
export class ComfyUIError extends Error {
  public readonly code: string;
  public readonly details?: unknown;

  constructor(message: string, code: string = 'COMFYUI_ERROR', details?: unknown) {
    super(message);
    this.name = 'ComfyUIError';
    this.code = code;
    this.details = details;
  }
}

/**
 * Error thrown when template validation fails
 */
export class TemplateValidationError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'TEMPLATE_VALIDATION_ERROR', details);
    this.name = 'TemplateValidationError';
  }
}

/**
 * Error thrown when parameter validation fails
 */
export class ParameterValidationError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'PARAMETER_VALIDATION_ERROR', details);
    this.name = 'ParameterValidationError';
  }
}

/**
 * Error thrown when template parsing fails
 */
export class TemplateParsingError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'TEMPLATE_PARSING_ERROR', details);
    this.name = 'TemplateParsingError';
  }
}

/**
 * Error thrown when API requests fail
 */
export class APIError extends ComfyUIError {
  public readonly statusCode: number | undefined;

  constructor(message: string, statusCode?: number, details?: unknown) {
    super(message, 'API_ERROR', details);
    this.name = 'APIError';
    this.statusCode = statusCode;
  }
}

/**
 * Error thrown when WebSocket connection fails
 */
export class ConnectionError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'CONNECTION_ERROR', details);
    this.name = 'ConnectionError';
  }
}

/**
 * Error thrown when execution times out
 */
export class TimeoutError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'TIMEOUT_ERROR', details);
    this.name = 'TimeoutError';
  }
}

/**
 * Error thrown when execution is interrupted
 */
export class ExecutionInterruptedError extends ComfyUIError {
  constructor(message: string, details?: unknown) {
    super(message, 'EXECUTION_INTERRUPTED_ERROR', details);
    this.name = 'ExecutionInterruptedError';
  }
}

/**
 * Wraps an unknown error in a ComfyUIError
 * @param error - Unknown error to wrap
 * @param code - Error code
 * @returns ComfyUIError instance
 */
export function wrapError(error: unknown, code: string = 'UNKNOWN_ERROR'): ComfyUIError {
  if (error instanceof ComfyUIError) {
    return error;
  }

  if (error instanceof Error) {
    return new ComfyUIError(error.message, code, { originalError: error });
  }

  return new ComfyUIError(String(error), code, { originalValue: error });
}

/**
 * Checks if an error is a specific type of ComfyUIError
 * @param error - Error to check
 * @param errorClass - Error class to check against
 * @returns True if error is of the specified type
 */
export function isErrorType<T extends ComfyUIError>(
  error: unknown,
  errorClass: new (...args: any[]) => T
): error is T {
  return error instanceof errorClass;
}

/**
 * Creates a timeout promise that rejects after the specified time
 * @param ms - Timeout in milliseconds
 * @param message - Timeout error message
 * @returns Promise that rejects with TimeoutError
 */
export function createTimeoutPromise(ms: number, message: string = 'Operation timed out'): Promise<never> {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(new TimeoutError(`${message} (${ms}ms)`));
    }, ms);
  });
}

/**
 * Wraps a promise with a timeout
 * @param promise - Promise to wrap
 * @param timeoutMs - Timeout in milliseconds
 * @param timeoutMessage - Timeout error message
 * @returns Promise that resolves or rejects with timeout
 */
export async function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  timeoutMessage?: string
): Promise<T> {
  return Promise.race([
    promise,
    createTimeoutPromise(timeoutMs, timeoutMessage)
  ]);
}

/**
 * Retries an async operation with exponential backoff
 * @param operation - Async operation to retry
 * @param maxAttempts - Maximum number of attempts
 * @param baseDelay - Base delay in milliseconds
 * @param maxDelay - Maximum delay in milliseconds
 * @returns Promise resolving to operation result
 */
export async function retry<T>(
  operation: () => Promise<T>,
  maxAttempts: number = 3,
  baseDelay: number = 1000,
  maxDelay: number = 10000
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      if (attempt === maxAttempts) {
        throw lastError;
      }

      const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}
